package com.parmal.proxy.handler.http;

import com.parmal.proxy.configuration.HttpProxyConfiguration;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.*;

public class HttpRequestDeliveryHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private static Logger logger = LoggerFactory.getLogger(HttpRequestDeliveryHandler.class);

    private Channel remoteChannel;//目标服务器连接

    private Bootstrap forwardBootstrap;

    //请求 响应交换管道
    private HttpForwardAndReceiveHandler exchangeHandler;


    private HttpProxyConfiguration configuration;

    public HttpRequestDeliveryHandler(HttpProxyConfiguration configuration, Bootstrap forwardBootstrap) {
        this.configuration = configuration;
        this.forwardBootstrap = forwardBootstrap;
    }

    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, FullHttpRequest message) throws
            Exception {
        logger.info("转发节点=>{}", message.getClass());
        if (!message.decoderResult().isSuccess()) {
            logger.error("请求解析失败=>{}", message.decoderResult());
            return;
        }

        //解析端口 构造转发请求
        final FullHttpRequest defaultHttpRequest = message.duplicate();
        defaultHttpRequest.retain();//引用 +1
        URL targetUrl = new URL((String) message.headers().get("target-Url"));
        int port = targetUrl.getPort() == -1 ? (targetUrl.getProtocol().equals("https") ? 443 : 80) :
                targetUrl.getPort();
        logger.info("解析出目标服务器uri=>{}", targetUrl);
        String host = targetUrl.getHost();
        defaultHttpRequest.setUri(targetUrl.toString());
        defaultHttpRequest.headers().set(HttpHeaderNames.HOST, host);

        if (this.remoteChannel != null && this.remoteChannel.isWritable() && this.isIdenticalInetAddress(
                (InetSocketAddress) this.remoteChannel.remoteAddress(), host, port)) {
            //存在通道，并且通道可用 并且通道与本次请求的地址一致，则可复用通道

            logger.info("通道可用");

            //确定此次请求结束后是否关闭连接
            this.setConnection(message.headers());
            //转发请求
            this.exchangeHandler.forwardRequest(defaultHttpRequest);
        } else {
            //如果通道不存在或者已断开
            if (this.remoteChannel != null) {
                logger.info("关闭原通道");
                this.remoteChannel.close().sync();
            }
            logger.info("创建通道");
            //连接remote Server
            ChannelFuture connectFuture = this.forwardBootstrap.connect(host, port);
            this.remoteChannel = connectFuture.channel();
            //添加响应监听
            this.exchangeHandler = new HttpForwardAndReceiveHandler(channelHandlerContext.channel());
            this.remoteChannel.pipeline().addLast(this.exchangeHandler);
            this.setConnection(message.headers());
            //连接建立成功后，发送请求
            connectFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) {
                    if (channelFuture.isSuccess()) {
                        channelFuture.channel().writeAndFlush(defaultHttpRequest);//转发请求
                    }
                }
            });

        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 根据请求头重 connection 值来决定请求响应结束后是否关闭连接
     *
     * @param headers
     */
    private void setConnection(HttpHeaders headers) {
        if (this.exchangeHandler != null && !HttpHeaderValues.KEEP_ALIVE.toString().equalsIgnoreCase(headers.get
                (HttpHeaderNames.CONNECTION)
                .toString())) {
            //如果需要保持tcp连接
            logger.info("请求结束后关闭连接");
            this.exchangeHandler.disconnectRemoteAndClient();
        }
    }

    /**
     * 判断一个address 与 一个 host&port 是否是一个地址
     *
     * @param address
     * @param host
     * @param port
     * @return
     */
    private boolean isIdenticalInetAddress(InetSocketAddress address, String host, int port) {
        if (address == null) {
            return false;
        }
        boolean isIdentical = address.getHostName().equals(host) && address.getPort() == port;
        logger.info("此次链接与上一次地址是否一致=>{}", isIdentical);
        return isIdentical;
    }

}
